package main

import (
	"fmt"
	"time"
)

// slice、map、channel等集合数据结构，它内部引用了对应数组的结构(指针) 创建初始化使用make()
// 虽然作为参数是值传递，但可以改变内部的元素，内部元素地址是一样的，不需要*传递指针
func worker(id int, c chan int) { //channel做参数
	// for {
	// 	n, ok := <-c //close后获取数据是 channnel 类型的默认值,ok判断channel是否关闭
	// 	if !ok {
	// 		break
	// 	}
	// 	fmt.Printf("Worker %d received %c\n",
	// 		id, n)
	// }

	for n := range c { //close会退出遍历,等效上面的for代码
		fmt.Printf("Worker %d received %c\n",
			id, n)
	}
}

// 约束chan<- int  返回的channel只能用来发数据
// 约束<-chan int  返回的channel只能用来收数据
func createWorker(id int) chan<- int { //channel做返回值
	c := make(chan int) //创建channel
	go worker(id, c)    //无缓存需要goroutine接收,channel用来协程间的通信
	return c
}

func chanDemo() {
	var channels [10]chan<- int
	for i := 0; i < 10; i++ {
		channels[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}

	time.Sleep(time.Millisecond)
}

// 容量(capacity)代表Channel容纳的最多的元素的数量，代表Channel的缓存的大小。
// 如果没有设置容量(默认)，或者容量设置为0, 说明Channel没有缓存，只有sender和receiver都准备好了后它们的通讯(communication)才会发生(Blocking)。
// 默认情况下(无缓存)，发送和接收都会一直阻塞着，直到另一方准备好。这种方式可以用来在gororutine中进行同步，而不必使用显示的锁或者条件变量。
// 如果设置了缓存，就有可能不发生阻塞，只有buffer满了后 send才会阻塞， 而只有缓存空了后receive才会阻塞。一个nil channel不会通信。
// 可以通过内建的close方法可以关闭Channel。
// 你可以在多个goroutine从/往 一个channel 中 receive/send 数据, 不必考虑额外的同步措施。
// Channel可以作为一个先入先出(FIFO)的队列，接收的数据和发送的数据的顺序是一致的。
func bufferedChannel() {
	c := make(chan int, 3) //设置缓存3，可以容纳三元素，尽量避免阻塞提升性能
	go worker(0, c)        //同一时刻channel必须有读端和写端，读写必须在两个协程中
	c <- 'a'               //发送/接收channel是阻塞的(原子性)，（无缓存时）必须等待其他协程去接收，两个channel顺序不对，容易造成互相等待，死锁，简单的操作可以在写一个协程用来等待，就不会卡
	c <- 'b'
	c <- 'c'
	c <- 'd'
	time.Sleep(time.Millisecond)
}

func channelClose() {
	c := make(chan int)
	go worker(0, c)
	c <- 'a'
	c <- 'b'
	c <- 'c'
	c <- 'd'
	close(c) //告诉接收方，发完了，close后取数据是channnel 类型的默认值
	time.Sleep(time.Millisecond)
}

func main() {
	fmt.Println("Channel as first-class citizen")
	chanDemo()
	fmt.Println("Buffered channel")
	bufferedChannel()
	fmt.Println("Channel close and range")
	channelClose()
}
